﻿using AMEC.PCSoftware.CommunicationProtocol.CrazyHein.FINS.IOUtility;
using System.Runtime.InteropServices;

namespace AMEC.PCSoftware.CommunicationProtocol.CrazyHein.FINS.Message
{
    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MESSAGE_HEADER_T
    {
        public byte cookie0;
        public byte cookie1;
        public byte cookie2;
        public byte cookie3;
        public uint length;
        public uint type;
        public uint error_code;

        public MESSAGE_HEADER_T()
        {
            cookie0 = System.Text.Encoding.ASCII.GetBytes("FINS")[0];
            cookie1 = System.Text.Encoding.ASCII.GetBytes("FINS")[1];
            cookie2 = System.Text.Encoding.ASCII.GetBytes("FINS")[2];
            cookie3 = System.Text.Encoding.ASCII.GetBytes("FINS")[3];
            length = 0;
            type = Message.COMMAND;
            error_code = 0;
        }
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct MESSAGE_T
    {
        public byte cookie0;
        public byte cookie1;
        public byte cookie2;
        public byte cookie3;
        public uint length;
        public uint type;
        public uint error_code;
        [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 2012, ArraySubType = UnmanagedType.U1)]
        public byte[] data;

        public MESSAGE_T()
        {
            cookie0 = System.Text.Encoding.ASCII.GetBytes("FINS")[0];
            cookie1 = System.Text.Encoding.ASCII.GetBytes("FINS")[1];
            cookie2 = System.Text.Encoding.ASCII.GetBytes("FINS")[2];
            cookie3 = System.Text.Encoding.ASCII.GetBytes("FINS")[3];
            length = 0;
            type = Message.COMMAND;
            error_code = 0;
            data = new byte[2012];
        }
    }

    public class Message
    {
        public readonly static bool IsLittleEndian = BitConverter.IsLittleEndian;
        public readonly static uint HANDSHAKE_RSQ = 0x00000000;
        public readonly static uint HANDSHAKE_RES = IsLittleEndian ? (uint)0x01000000 : 0x00000001;
        public readonly static uint COMMAND = IsLittleEndian ? (uint)0x02000000 : 0x00000002;

        public static ushort SWAP16(ushort x)
        {
            return IsLittleEndian ? (ushort)(((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8)) : x;
        }

        public static uint SWAP32(uint x)
        {
            return IsLittleEndian ? ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24) : x;
        }

        public static ReadOnlyMemory<byte> PARSE_RESPONSE(ReadOnlyMemory<byte> message, out MESSAGE_HEADER_T header)
        {
            if (message.Length < Marshal.SizeOf<MESSAGE_HEADER_T>())
                throw new FINSException(FINS_EXCEPTION_CODE_T.INCOMPLETE_RESPONSE_MESSAGE);
            header = MemoryMarshal.Read<MESSAGE_HEADER_T>(message.Span);
            if (header.cookie0 != 'F' || header.cookie1 != 'I' || header.cookie2 != 'N' || header.cookie3 != 'S')
                throw new FINSException(FINS_EXCEPTION_CODE_T.UNKNOWN_MESSAGE_HEADER);
            if(Message.SWAP32(header.length) != message.Length - 8)
                throw new FINSException(FINS_EXCEPTION_CODE_T.INCOMPLETE_RESPONSE_MESSAGE);
            return message.Slice(Marshal.SizeOf<MESSAGE_HEADER_T>());
        }

        public static int RECV(SocketInterface port, byte[] buffer, int offset)
        {
            port.Receive(buffer, offset, Marshal.SizeOf<MESSAGE_HEADER_T>());
            MESSAGE_HEADER_T header = MemoryMarshal.Read<MESSAGE_HEADER_T>(buffer.AsSpan<byte>(offset));
            var dataLength = Message.SWAP32(header.length);
            if (dataLength > 8)
                port.Receive(buffer, Marshal.SizeOf<MESSAGE_HEADER_T>() + offset, (int)(dataLength - 8));
            return Marshal.SizeOf<MESSAGE_HEADER_T>() + (int)(dataLength - 8);
        }
    }
}
